Skip to content

Conversation

@tobixdev
Copy link
Contributor

@tobixdev tobixdev commented Oct 7, 2025

Which issue does this PR close?

This is a follow-up to #8543 . There we discussed two outstanding issues that this PR tries to address.

Rationale for this change

Address the points from the PR.

What changes are included in this PR?

  1. Fix "Incorrect Behavior of Collecting a filtered iterator to a BooleanArray" #8543 (comment) : Improve benchmarks (use a dynamically dispatched iterator to avoid Vec-specific optimizations)
  2. Fix "Incorrect Behavior of Collecting a filtered iterator to a BooleanArray" #8543 (comment) : Also use ExactSizeIterator for the PrimitiveArray

Are these changes tested?

Existing test for PrimitiveArray

Are there any user-facing changes?

Yes, PrimitiveArray::from_trusted_len_iter is more restrictive.

@github-actions github-actions bot added the arrow Changes to the arrow crate label Oct 7, 2025
@tobixdev tobixdev changed the title 8543 follow up Follow-up to 8543 Oct 7, 2025
@alamb
Copy link
Contributor

alamb commented Oct 7, 2025

🤖 ./gh_compare_arrow.sh Benchmark Script Running
Linux aal-dev 6.14.0-1016-gcp #17~24.04.1-Ubuntu SMP Wed Sep 3 01:55:36 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing 8543-follow-up (716735f) to ba22a21 diff
BENCH_NAME=array_from
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental --bench array_from
BENCH_FILTER=
BENCH_BRANCH_NAME=8543-follow-up
Results will be posted here when complete

@alamb alamb changed the title Follow-up to 8543 Improve comments and change PrimitiveArray::from_trusted_len_iter to take an ExactSizeIterator Oct 7, 2025
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tobixdev -- I also kicked off some benchmarks

where
P: std::borrow::Borrow<Option<<T as ArrowPrimitiveType>::Native>>,
I: IntoIterator<Item = P>,
I: ExactSizeIterator<Item = P>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the rationale for this change? I am thinking that maybe someone who had an iterator that knew its length but did not implement ExactSizeIterator would have to change their code

Maybe that is ok, but the implementation doesn't even seem to use any of the methods on ExactSizeIterator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we just mentioned that as a side note in the last PR. I think it has two advantages:

  • Users have at least some help from the type system to avoid safety issues.
  • It's consistent with BooleanArray

However, I am also happy with the old API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think that some of the functions could make use of len instead of size_hint. If we choose to keep ExactSizeIterator I'll go through them.

I think another cause of incompatibility is caused by users needing to call .into_iter() manually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbrobbel Do you have any more thoughts on which iterator bound we should use?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use TrustedLen, but we can't because it's nightly-only. So the next best things is ExactSizeIterator for iterators that know their exact length. However there is a note in the docs:

Note that this trait is a safe trait and as such does not and cannot guarantee that the returned length is correct. This means that unsafe code must not rely on the correctness of Iterator::size_hint. The unstable and unsafe TrustedLen trait gives this additional guarantee.

Since this method is marked unsafe we require users to make sure their iterators are TrustedLen. This means:

The iterator reports a size hint where it is either exact (lower bound is equal to upper bound), or the upper bound is None. The upper bound must only be None if the actual iterator length is larger than usize::MAX. In that case, the lower bound must be usize::MAX, resulting in an Iterator::size_hint() of (usize::MAX, None).

The motivation to use ExactSizeIterator here (I think) is to use ExactSizeIterator::len instead of Iterator::size_hint. This has the same "can't be None" requirement on the upper bound reported by the size hint.

For the bound we can use I: IntoIterator<Item = P, IntoIter: ExactSizeIterator>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the bound we can use I: IntoIterator<Item = P, IntoIter: ExactSizeIterator>

+1 !

c50a02b

@alamb
Copy link
Contributor

alamb commented Oct 7, 2025

🤖: Benchmark completed

Details

group                                  8543-follow-up                         main
-----                                  --------------                         ----
BooleanArray::from_iter                2.51     59.9±0.15µs        ? ?/sec    1.00     23.9±0.08µs        ? ?/sec
BooleanArray::from_trusted_len_iter    1.79     40.0±0.06µs        ? ?/sec    1.00     22.4±0.03µs        ? ?/sec
Int64Array::from_iter                  2.21     92.0±0.19µs        ? ?/sec    1.00     41.6±0.10µs        ? ?/sec
Int64Array::from_trusted_len_iter      1.92     36.4±0.06µs        ? ?/sec    1.00     19.0±0.05µs        ? ?/sec
array_from_vec 128                     1.00    157.6±0.29ns        ? ?/sec    1.04    163.3±0.43ns        ? ?/sec
array_from_vec 256                     1.00    166.5±0.38ns        ? ?/sec    1.03    171.3±0.25ns        ? ?/sec
array_from_vec 512                     1.00    220.4±0.33ns        ? ?/sec    1.05    230.3±0.25ns        ? ?/sec
array_string_from_vec 128              1.00   1100.8±2.55ns        ? ?/sec    1.09   1197.5±1.43ns        ? ?/sec
array_string_from_vec 256              1.00   1890.3±3.01ns        ? ?/sec    1.02   1935.5±3.37ns        ? ?/sec
array_string_from_vec 512              1.05      3.4±0.02µs        ? ?/sec    1.00      3.2±0.01µs        ? ?/sec
decimal128_array_from_vec 32768        1.00     99.4±0.37µs        ? ?/sec    1.00     99.0±0.52µs        ? ?/sec
decimal256_array_from_vec 32768        1.02      3.9±0.03µs        ? ?/sec    1.00      3.9±0.01µs        ? ?/sec
decimal32_array_from_vec 32768         1.00     85.7±0.10µs        ? ?/sec    1.00     85.7±0.15µs        ? ?/sec
decimal64_array_from_vec 32768         1.00     97.3±0.24µs        ? ?/sec    1.00     97.6±0.37µs        ? ?/sec
struct_array_from_vec 1024             1.05      8.8±0.02µs        ? ?/sec    1.00      8.4±0.03µs        ? ?/sec
struct_array_from_vec 128              1.06  1972.7±10.11ns        ? ?/sec    1.00   1854.4±2.60ns        ? ?/sec
struct_array_from_vec 256              1.03      2.9±0.04µs        ? ?/sec    1.00      2.8±0.00µs        ? ?/sec
struct_array_from_vec 512              1.05      4.9±0.08µs        ? ?/sec    1.00      4.6±0.01µs        ? ?/sec

@tobixdev
Copy link
Contributor Author

Thanks for merging-in the latest main @alamb !

Do you see any reason why the integration tests are failing with these changes or was that a hiccup?

@alamb
Copy link
Contributor

alamb commented Nov 17, 2025

Thanks for merging-in the latest main @alamb !

Do you see any reason why the integration tests are failing with these changes or was that a hiccup?

probably the intermittent failure

@alamb
Copy link
Contributor

alamb commented Dec 11, 2025

I merged up to resolve some conflicts

@alamb
Copy link
Contributor

alamb commented Dec 11, 2025

run benchmark array_iter

@alamb-ghbot
Copy link

🤖 ./gh_compare_arrow.sh gh_compare_arrow.sh Running
Linux aal-dev 6.14.0-1018-gcp #19~24.04.1-Ubuntu SMP Wed Sep 24 23:23:09 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing 8543-follow-up (31efc41) to ca4a0ae diff
BENCH_NAME=array_iter
BENCH_COMMAND=cargo bench --features=arrow,async,test_common,experimental --bench array_iter
BENCH_FILTER=
BENCH_BRANCH_NAME=8543-follow-up
Results will be posted here when complete

@alamb
Copy link
Contributor

alamb commented Dec 11, 2025

(whoops wrong PR)

@alamb-ghbot
Copy link

🤖: Benchmark completed

Details

group                                                                       8543-follow-up                           main
-----                                                                       --------------                           ----
boolean mixed true and false/nonnull any black box item and predicate       1.00     78.9±1.19µs 791.9 MElem/sec     1.00     78.7±0.40µs 793.8 MElem/sec
boolean mixed true and false/nonnull any black box only result              1.00     34.9±0.15µs 1789.4 MElem/sec    1.00     35.0±0.17µs 1785.3 MElem/sec
boolean mixed true and false/nonnull fold black box item                    1.00     69.9±0.38µs 894.0 MElem/sec     1.00     69.9±0.76µs 893.5 MElem/sec
boolean mixed true and false/nonnull fold black box item and fold result    1.00    105.0±1.02µs 595.0 MElem/sec     1.00    104.9±0.37µs 595.9 MElem/sec
boolean mixed true and false/nonnull fold black box only result             1.00     39.4±0.46µs 1586.8 MElem/sec    1.00     39.3±0.29µs 1589.7 MElem/sec
boolean mixed true and false/null any black box item and predicate          1.00    420.1±2.15µs 148.8 MElem/sec     1.00    420.3±1.95µs 148.7 MElem/sec
boolean mixed true and false/null any black box only result                 1.00     35.0±0.34µs 1787.3 MElem/sec    1.02     35.5±3.40µs 1759.7 MElem/sec
boolean mixed true and false/null fold black box item                       1.00    387.7±1.26µs 161.2 MElem/sec     1.00    388.9±3.45µs 160.7 MElem/sec
boolean mixed true and false/null fold black box item and fold result       1.00    335.1±1.92µs 186.5 MElem/sec     1.00    335.5±1.66µs 186.3 MElem/sec
boolean mixed true and false/null fold black box only result                1.00    293.8±1.99µs 212.7 MElem/sec     1.00    294.6±5.07µs 212.2 MElem/sec
int16/nonnull any black box item and predicate                              1.01     58.6±0.41µs 1067.3 MElem/sec    1.00     58.2±0.86µs 1073.5 MElem/sec
int16/nonnull any black box only result                                     1.00     25.6±0.07µs  2.4 GElem/sec      1.00     25.7±0.12µs  2.4 GElem/sec
int16/nonnull fold black box item                                           1.00     39.6±0.18µs 1577.8 MElem/sec    1.00     39.6±0.34µs 1578.9 MElem/sec
int16/nonnull fold black box item and fold result                           1.00     89.9±0.37µs 695.0 MElem/sec     1.00     89.9±0.25µs 694.9 MElem/sec
int16/nonnull fold black box only result                                    1.00      2.3±0.04µs 26.8 GElem/sec      1.00      2.3±0.01µs 26.9 GElem/sec
int16/null any black box item and predicate                                 1.00    369.7±5.62µs 169.1 MElem/sec     1.00    369.2±2.44µs 169.3 MElem/sec
int16/null any black box only result                                        1.00    300.9±2.42µs 207.7 MElem/sec     1.00    301.4±4.30µs 207.4 MElem/sec
int16/null fold black box item                                              1.00    376.2±2.93µs 166.1 MElem/sec     1.00    376.3±2.18µs 166.1 MElem/sec
int16/null fold black box item and fold result                              1.00    382.2±2.52µs 163.5 MElem/sec     1.00    381.9±1.17µs 163.7 MElem/sec
int16/null fold black box only result                                       1.00    279.0±3.27µs 224.0 MElem/sec     1.00    278.1±1.48µs 224.8 MElem/sec
int32/nonnull any black box item and predicate                              1.00     58.6±0.67µs 1065.9 MElem/sec    1.00     58.7±0.41µs 1064.0 MElem/sec
int32/nonnull any black box only result                                     1.00     25.7±0.36µs  2.4 GElem/sec      1.00     25.8±0.33µs  2.4 GElem/sec
int32/nonnull fold black box item                                           1.00     48.3±0.32µs 1294.7 MElem/sec    1.00     48.3±0.55µs 1293.5 MElem/sec
int32/nonnull fold black box item and fold result                           1.00     89.5±0.27µs 698.0 MElem/sec     1.00     89.5±0.44µs 698.0 MElem/sec
int32/nonnull fold black box only result                                    1.00      4.6±0.06µs 13.4 GElem/sec      1.00      4.6±0.05µs 13.4 GElem/sec
int32/null any black box item and predicate                                 1.00    408.3±2.12µs 153.1 MElem/sec     1.00    408.3±1.99µs 153.1 MElem/sec
int32/null any black box only result                                        1.00    304.1±0.88µs 205.6 MElem/sec     1.00    304.2±0.89µs 205.5 MElem/sec
int32/null fold black box item                                              1.00    370.0±4.54µs 168.9 MElem/sec     1.00    369.6±1.58µs 169.1 MElem/sec
int32/null fold black box item and fold result                              1.00    352.2±3.81µs 177.4 MElem/sec     1.00    352.3±1.96µs 177.4 MElem/sec
int32/null fold black box only result                                       1.00    322.5±1.44µs 193.8 MElem/sec     1.00    322.6±1.27µs 193.7 MElem/sec
int64/nonnull any black box item and predicate                              1.00     61.6±0.35µs 1014.3 MElem/sec    1.00     61.9±1.21µs 1010.1 MElem/sec
int64/nonnull any black box only result                                     1.00     26.8±0.19µs  2.3 GElem/sec      1.00     26.8±0.09µs  2.3 GElem/sec
int64/nonnull fold black box item                                           1.00     46.0±0.38µs 1358.2 MElem/sec    1.00     46.0±0.28µs 1357.7 MElem/sec
int64/nonnull fold black box item and fold result                           1.00     89.6±0.24µs 697.9 MElem/sec     1.00     89.6±0.30µs 697.5 MElem/sec
int64/nonnull fold black box only result                                    1.00      9.3±0.08µs  6.5 GElem/sec      1.00      9.3±0.04µs  6.5 GElem/sec
int64/null any black box item and predicate                                 1.00    316.1±1.28µs 197.7 MElem/sec     1.00    316.9±3.63µs 197.2 MElem/sec
int64/null any black box only result                                        1.00    300.3±5.98µs 208.1 MElem/sec     1.00   300.7±10.98µs 207.9 MElem/sec
int64/null fold black box item                                              1.00    374.4±1.27µs 166.9 MElem/sec     1.00    376.2±9.33µs 166.1 MElem/sec
int64/null fold black box item and fold result                              1.00    364.3±1.24µs 171.5 MElem/sec     1.00    364.3±2.91µs 171.6 MElem/sec
int64/null fold black box only result                                       1.00    334.5±2.25µs 186.8 MElem/sec     1.00    334.7±1.92µs 186.7 MElem/sec
int8/nonnull any black box item and predicate                               1.01     71.1±3.89µs 879.5 MElem/sec     1.00     70.2±0.28µs 890.6 MElem/sec
int8/nonnull any black box only result                                      1.00     37.0±1.35µs 1688.1 MElem/sec    1.00     36.8±0.27µs 1696.3 MElem/sec
int8/nonnull fold black box item                                            1.00     39.7±0.14µs 1573.7 MElem/sec    1.00     39.7±0.26µs 1574.4 MElem/sec
int8/nonnull fold black box item and fold result                            1.00     89.4±0.22µs 698.9 MElem/sec     1.00     89.4±0.14µs 699.2 MElem/sec
int8/nonnull fold black box only result                                     1.00      2.2±0.01µs 27.9 GElem/sec      1.00      2.2±0.02µs 27.8 GElem/sec
int8/null any black box item and predicate                                  1.00    429.4±1.73µs 145.5 MElem/sec     1.00    429.3±1.01µs 145.6 MElem/sec
int8/null any black box only result                                         1.00    461.4±1.78µs 135.5 MElem/sec     1.00    462.0±4.84µs 135.3 MElem/sec
int8/null fold black box item                                               1.00    395.6±3.39µs 158.0 MElem/sec     1.00    394.7±0.82µs 158.3 MElem/sec
int8/null fold black box item and fold result                               1.00    396.0±1.93µs 157.8 MElem/sec     1.00    395.8±1.89µs 157.9 MElem/sec
int8/null fold black box only result                                        1.00    274.9±0.67µs 227.3 MElem/sec     1.00    274.9±1.30µs 227.4 MElem/sec
string view with len 16/nonnull any black box item and predicate            1.00    149.6±0.58µs 417.8 MElem/sec     1.00    149.8±0.71µs 417.3 MElem/sec
string view with len 16/nonnull any black box only result                   1.00     70.2±0.41µs 890.1 MElem/sec     1.00     70.4±0.75µs 887.5 MElem/sec
string view with len 16/nonnull fold black box item                         1.00    132.9±0.82µs 470.1 MElem/sec     1.01    133.8±5.05µs 467.1 MElem/sec
string view with len 16/nonnull fold black box item and fold result         1.00    151.8±1.97µs 411.7 MElem/sec     1.00    151.7±0.89µs 411.9 MElem/sec
string view with len 16/nonnull fold black box only result                  1.00     61.5±0.25µs 1016.4 MElem/sec    1.00     61.6±0.48µs 1014.9 MElem/sec
string view with len 16/null any black box item and predicate               1.00    455.9±1.29µs 137.1 MElem/sec     1.00    456.2±3.12µs 137.0 MElem/sec
string view with len 16/null any black box only result                      1.00    354.5±1.32µs 176.3 MElem/sec     1.00    355.1±3.75µs 176.0 MElem/sec
string view with len 16/null fold black box item                            1.00    467.3±1.93µs 133.7 MElem/sec     1.00    467.1±1.87µs 133.8 MElem/sec
string view with len 16/null fold black box item and fold result            1.00    429.1±1.45µs 145.7 MElem/sec     1.00    429.3±0.89µs 145.6 MElem/sec
string view with len 16/null fold black box only result                     1.00    363.7±4.58µs 171.8 MElem/sec     1.00    365.3±3.96µs 171.1 MElem/sec
string with len 16/nonnull any black box item and predicate                 1.00    122.9±0.43µs 508.6 MElem/sec     1.00    123.1±0.72µs 507.7 MElem/sec
string with len 16/nonnull any black box only result                        1.00     26.4±0.20µs  2.3 GElem/sec      1.00     26.5±0.22µs  2.3 GElem/sec
string with len 16/nonnull fold black box item                              1.00    121.5±2.23µs 514.3 MElem/sec     1.00    121.4±2.29µs 514.7 MElem/sec
string with len 16/nonnull fold black box item and fold result              1.00    137.9±2.50µs 453.4 MElem/sec     1.00    138.3±7.49µs 451.9 MElem/sec
string with len 16/nonnull fold black box only result                       1.00     14.2±0.41µs  4.3 GElem/sec      1.01     14.3±0.36µs  4.3 GElem/sec
string with len 16/null any black box item and predicate                    1.00    452.9±7.35µs 138.0 MElem/sec     1.00    453.0±1.78µs 138.0 MElem/sec
string with len 16/null any black box only result                           1.00    273.3±5.49µs 228.7 MElem/sec     1.00    272.4±0.60µs 229.4 MElem/sec
string with len 16/null fold black box item                                 1.00    453.4±2.18µs 137.9 MElem/sec     1.00    454.4±6.84µs 137.5 MElem/sec
string with len 16/null fold black box item and fold result                 1.00    458.7±1.48µs 136.3 MElem/sec     1.00    459.3±2.87µs 136.1 MElem/sec
string with len 16/null fold black box only result                          1.00    320.7±4.08µs 194.9 MElem/sec     1.00    321.0±6.06µs 194.7 MElem/sec

@tobixdev
Copy link
Contributor Author

I already forget about this one! Is there any consent on whether we want to use ExactSizeIterator?

If we don't want to change the API I can also close the PR to de-clutter the list.

@alamb
Copy link
Contributor

alamb commented Dec 12, 2025

I already forget about this one! Is there any consent on whether we want to use ExactSizeIterator?

If we don't want to change the API I can also close the PR to de-clutter the list.

I would say in general if we don't see performance improvements we should probably close this PR / not make the change

@tobixdev tobixdev closed this Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arrow Changes to the arrow crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants